/*
 *  StaDiUM - State Diagram Unified Modeler
 *  Copyright (C) 2007  Stefan Bucur, Mihai Balan, Claudiu Tanase
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.

 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 *  File Information
 *
 *  Last Modified On: $Date: 2007-06-04 18:04:41 +0200 (Mon, 04 Jun 2007) $
 *  Last Modified By: $Author: mihai.balan $
 */

package ro.pub.cs.stadium.output.verilog;

import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map;

import ro.pub.cs.stadium.document.IDocument;
import ro.pub.cs.stadium.document.IState;
import ro.pub.cs.stadium.document.ITransition;
import ro.pub.cs.stadium.document.logic.Document;
import ro.pub.cs.stadium.document.logic.State;
import ro.pub.cs.stadium.document.logic.Transition;
import ro.pub.cs.stadium.document.logic.Variable;
import ro.pub.cs.stadium.plugin.IOutputPlugin;
import ro.pub.cs.stadium.plugin.PluginInfo;
import ro.pub.cs.stadium.plugin.PluginSettings;
import ro.pub.cs.stadium.plugin.PluginType;

public class VerilogOutputPlugin implements IOutputPlugin {
	private final String docFormat = "Verilog";
	
	private String NL = System.getProperty("line.separator");
	
	public Object getPlugin(PluginType type) {
		if (type == PluginType.OUTPUT)
			return this;
		else
			return null;
	}

	public PluginType[] getPluginImplementations() {
		return new PluginType[] {PluginType.OUTPUT};
	}

	public void initialise(PluginSettings settings) {
		// TODO Auto-generated method stub
	}

	public void writeDocument(IDocument document, OutputStream stream) {
		OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("US-ASCII"));
		Document doc;
		
		if (document instanceof Document)
			doc = (Document)document;
		else 
			return;
		
		try {
			writeHeader(doc, writer);
			writer.flush();
			writeVariablesDeclaration(doc, writer);
			writer.flush();
			writeAdvanceLogic(doc, writer);
			writer.flush();
			writeTransitionLogic(doc, writer);
			writer.flush();
			writer.write("endmodule" + NL);
			writer.flush();
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void writeHeader(Document doc, OutputStreamWriter out)
			throws IOException {
		StringBuffer varList = new StringBuffer();
		out.write("`timescale 1s/1s" + NL);
		
		out.write("module verilog_module(");
		for (Variable var : doc.getVariableList().getVariables()) {
			if (varList.indexOf(var.getName()) < 0) {
				varList.append(var.getName());
				varList.append(", ");
			}
		}
		
		varList.append("CLK");
		varList.append(");");
		out.write(varList.toString());
		out.write(NL + NL);
	}
	
	private void writeVariablesDeclaration(Document doc, OutputStreamWriter writer)
			throws IOException {
		State state;
		StringBuffer varList = new StringBuffer(10);
		
		//declaration of inputs
		writer.write("//input ports" + NL);
		writer.write("input CLK, RESET;" + NL);
		for (Variable var : doc.getVariableList().getVariables()) {
			if (!var.isOut() && varList.indexOf(var.getName()) < 0) {
				varList.append("input " + var.getName() + ";");
				varList.append(NL);
			}
		}
		writer.write(varList.toString());
		varList.delete(0, varList.length()-1);
		
		//output variables;
		writer.write("//output ports" + NL);
		for (Variable var : doc.getVariableList().getVariables()) {
			if (var.isOut() && varList.indexOf(var.getName()) < 0) {
				varList.append("output " + var.getName() + ";");
				varList.append(NL);
			}
		}
		writer.write(varList.toString());
		varList.delete(0, varList.length()-1);
		
		//registers for output variables;
		writer.write("//Registers for the output variables" + NL);
		for (Variable var : doc.getVariableList().getVariables()) {
			if (var.isOut() && varList.indexOf(var.getName()) < 0) {
				varList.append("reg " + var.getName() + ";");
				varList.append(NL);
			}
		}
		writer.write(varList.toString());
		varList.delete(0, varList.length()-1);
		
		//registers for states, 2/state
		writer.write("//state registers" + NL);
		for (IState s : doc.getStates().values()){
			state = (State)s;
			if (state != null && varList.indexOf(state.getName()) < 0){
				varList.append("reg " + state.getName() + ", next_" + state.getName() + ";");
				varList.append(NL);
			}
		}
		varList.append(NL);
		writer.write(varList.toString());
	}
	
	private void writeAdvanceLogic(Document doc, OutputStreamWriter out)
			throws IOException {
		boolean first = true;
		State s;
		Map<Variable, Boolean> initVars;
		
		out.write("always @(posedge CLK or posedge RESET)" + NL);
		out.write("begin" + NL);
		out.write("if (RESET) begin" + NL);
		out.write("//states reset" + NL);
		for(IState state:doc.getStates().values()) {
			s = (State)state;
			if (s != null) {
				if (first) {
					first = false;
					out.write(s.getName() + " = 1;" + NL);
				}
				else {
					out.write(s.getName() + " = 0;" + NL);
				}
			}
		}
		//output variables reset
		out.write("//Output variables reset" + NL);
		if (!doc.getStates().values().isEmpty()) {
			s = (State)doc.getStates().values().iterator().next();
			initVars = s.getValues();
			if (initVars != null) {
				for(Variable var : initVars.keySet()) {
					out.write(var.getName() + " = ");
					out.write(initVars.get(var).booleanValue() == true ? "1;" : "0;");
					out.write(NL);
				}
			}
		}
		
		out.write("end else" + NL);
		out.write("begin" + NL);
		//states advance
		for(IState state:doc.getStates().values()) {
			s = (State)state;
			if (s != null) {
				out.write(state.getName()+ " = next_" + state.getName() + ";" + NL);
			}
		}
		
		out.write("end" + NL); //else
		out.write("end" + NL); //always
		out.write(NL);
	}
	
	private void writeOutputLogic(Document doc, OutputStreamWriter out)
			throws IOException {
		State state;
		Map<Variable, Boolean> stateVars;
		
		out.write("//Output variables logic" + NL);
		//foreach state:
		//if state => output = state_value
		for (IState s : doc.getStates().values()) {
			state = (State) s;
			if (state != null) {
				out.write("if (" + state.getName() + ")" + NL);
				out.write("begin" + NL);
				stateVars = state.getValues();
				if (stateVars != null) {
					for(Variable var : stateVars.keySet()) {
						if (stateVars.get(var) != null) {
							out.write(var.getName() + " = ");
							out.write(stateVars.get(var).booleanValue() == true ? "1;" : "0;");
							out.write(NL);
						}
					}
				}
				out.write("end"+NL);
			}
		}
	}
	
	private void writeTransitionLogic(Document doc, OutputStreamWriter out)
			throws IOException {
		Transition transition;
		State state;
		StringBuffer varBuffer = new StringBuffer();
		//adding variables
		varBuffer.append("always @(");
		for(Variable var : doc.getVariableList().getVariables()) {
			if (varBuffer.indexOf(var.getName()) < 0) {
				varBuffer.append(var.getName());
				varBuffer.append(" or ");
			}
		}
		//adding states
		for(IState s : doc.getStates().values()) {
			state = (State) s;
			if (state != null && varBuffer.indexOf(state.getName()) < 0) {
				varBuffer.append(state.getName());
				varBuffer.append(" or ");
			}
		}
		//deleting final " or "
		if (varBuffer.lastIndexOf("or") > 0)
			varBuffer.delete(varBuffer.length() - 5, varBuffer.length() - 1);
		varBuffer.append(")" + NL);
		
		out.write(varBuffer.toString());
		out.write("begin" + NL);
		
		//foreach transition
		//if transition_expression & initial_state => next_final_state = 1;
		for (ITransition t : doc.getTransitions().values()) {
			transition = (Transition) t;
			if (transition != null) {
				out.write("if (");
				out.write(transition.getStartPoint().getName());
				out.write(" & (");
				out.write(transition.getCondition().toString() + "))" + NL);
				out.write("next_" + transition.getEndPoint().getName() + " = 1;" + NL);
				out.write("else" + NL);
				out.write("next_" + transition.getEndPoint().getName() + " = 0;" + NL);
			}
		}
		
		writeOutputLogic(doc, out);
		
		out.write("end" + NL);
	}
	
/*	public static void main(String args[]) throws Exception {
		Document doc  = new Document();
		VariableList variables = new VariableList();
		State s1, s2;
		Transition t;
		
		variables.addVariable(new Variable("x", true));
		s1 = new State("State1", variables);
		s1.setActions("iX = 0;");
		doc.addState(s1);
		
		variables = new VariableList();
		variables.addVariable(new Variable("y", false));
		s2 = new State("State2", variables);
		//s2.setActions("iX = 1;");
		doc.addState(s2);
		
		t = new Transition("trans1", s1, s2);
		t.setCondition("x");
		doc.addTransition(t);
		
		VerilogOutputPlugin out = new VerilogOutputPlugin();
		OutputStream output = new FileOutputStream("C:\\out.txt"); 
			//new OutputStreamWriter(new FileOutputStream("out.txt"));
		out.writeDocument(doc, output);
	}*/

	public PluginInfo getPluginInfo() {
		return new PluginInfo("Verilog export plugin v0.1a", null);
	}

	/* (non-Javadoc)
	 * @see ro.pub.cs.stadium.plugin.IOutputPlugin#getDocumentFormat()
	 */
	public String getDocumentFormat() {
		return docFormat;
	}
	
	
}
